﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace SilentOrbit.ProtocolBuffers
{
    public class ProtoAnnotationGenerator
    {
        private HashSet<string> messageSet;
        private Queue<string> messageQueue;

        public void Generate(string directory, string outputPath)
        {
            string protoPath = Path.Combine(System.Environment.CurrentDirectory, directory);
            var files = Directory.GetFiles(protoPath, "*.proto", SearchOption.AllDirectories);

            var parser = new FileParser();
            var collection = parser.Import(files);

            messageSet = new HashSet<string>();
            messageQueue = new Queue<string>();

            using (var streamWriter = new StreamWriter(File.Open(outputPath, FileMode.Create), Encoding.UTF8))
            {
                streamWriter.NewLine = "\r\n";

                WriteMessagesByEnum(collection, streamWriter, "CSMessageEnum", "CS_");
                WriteMessagesByEnum(collection, streamWriter, "SCMessageEnum", "SC_");
            }
        }

        private void WriteMessagesByEnum(ProtoCollection collection, StreamWriter streamWriter, string enumName, string messagePrefix)
        {
            var csMessageEnum = collection.Enums[enumName];
            foreach (var protoEnumValue in csMessageEnum.Enums)
            {
                string messageName = protoEnumValue.Name.Replace(messagePrefix, "");
                if (messageSet.Contains(messageName))
                    continue;

                WriteMessage(collection, messageName, streamWriter);

                while (messageQueue.Count > 0)
                {
                    var subMessageName = messageQueue.Dequeue();
                    WriteMessage(collection, subMessageName, streamWriter);
                }
            }
        }

        private void WriteEnum(ProtoCollection collection, string enumName, StreamWriter streamWriter)
        {
            if (!collection.Enums.ContainsKey(enumName))
            {
                Console.WriteLine($"Neither message or enum： {enumName}");
                return;
            }

            var protoEnum = collection.Enums[enumName];

            streamWriter.WriteLine();
            string enumComments = protoEnum.Comments.Trim().Replace("\r", "").Replace("\n", " "); ;
            streamWriter.WriteLine($"---@class {enumName} {enumComments}");

            foreach (var protoEnumValue in protoEnum.Enums)
            {
                string comments = protoEnumValue.Comment.Trim().Replace("\r", "").Replace("\n", " "); ;
                streamWriter.WriteLine($"---@field public {protoEnumValue.Name} number 值:{protoEnumValue.Value} {comments}");
            }
        }

        private void WriteMessage(ProtoCollection collection, string messageName, StreamWriter streamWriter)
        {
            if (!collection.Messages.ContainsKey(messageName))
            {
                WriteEnum(collection, messageName, streamWriter);
                return;
            }

            var message = collection.Messages[messageName];

            streamWriter.WriteLine();
            string messageComments = message.Comments.Trim().Replace("\r", "").Replace("\n", " ");
            streamWriter.WriteLine($"---@class {messageName} {messageComments}");

            foreach (var keyFieldPair in message.Fields)
            {
                var field = keyFieldPair.Value;

                string array = field.Rule == FieldRule.Repeated ? "[]" : "";
                string see = "";
                if (!TryParseProtoType(field.ProtoTypeName, out string luaType))
                {
                    if (!messageSet.Contains(luaType))
                    {
                        messageQueue.Enqueue(luaType);
                        messageSet.Add(luaType);
                    }

                    if (collection.Enums.ContainsKey(luaType))
                    {
                        luaType = "number";
                        see = $"枚举类型: {field.ProtoTypeName}";
                    }
                }

                var fieldComments = field.Comments.Trim().Replace("\r", "").Replace("\n", " ");
                streamWriter.WriteLine($"---@field public {field.ProtoName} {luaType}{array} {fieldComments} {see}");
            }
        }

        /// <summary>
        /// 传入ProtoType，返回是否是内置的基础类型，并转成Lua类型
        /// </summary>
        /// <param name="type"></param>
        /// <param name="builtinType"></param>
        /// <returns></returns>
        private bool TryParseProtoType(string type, out string builtinType)
        {
            switch (type)
            {
                case "double":
                case "float":
                case "int32":
                case "int64":
                case "uint32":
                case "uint64":
                case "sint32":
                case "sint64":
                case "fixed32":
                case "fixed64":
                case "sfixed32":
                case "sfixed64":
                    builtinType =  "number";
                    return true;
                case "bool":
                    builtinType = "boolean";
                    return true;
                case "string":
                    builtinType = "string";
                    return true;
                case "bytes":
                    builtinType = "(unsupported type: bytes)";
                    return false;
                default:
                    builtinType =  type;
                    return false;
            }
        }

    }
}
